import Foundation

@_silgen_name("swift_getTypeByMangledNameInContext")
private func _getTypeByMangledNameInContext(
    _ name: UnsafePointer<UInt8>,
    _ nameLength: Int,
    genericContext: UnsafeRawPointer?,
    genericArguments: UnsafeRawPointer?) -> Any.Type?

struct MPStructMetaData{
    var kind: Int
    var typeDesc: UnsafeMutablePointer<MPStructDesc>
}

struct MPStructDesc{
    var flags : UInt32
    var parent : MPRelativeDirtPointer<UnsafeRawPointer>
    var name : MPRelativeDirtPointer<CChar>
    var accessFunctionPointer : MPRelativeDirtPointer<UnsafeRawPointer>
    var fieldDescriptor : MPRelativeDirtPointer<MPFieldDesc>
    var numFields : UInt32
    var fieldOffset: UInt32
    func getFieldOffsets(_ metadata: UnsafeRawPointer) -> UnsafePointer<Int> {
        return UnsafeRawPointer(metadata.assumingMemoryBound(to: Int.self).advanced(by: numericCast(self.fieldOffset))).assumingMemoryBound(to: Int.self)
    }
    var genericOffset: Int {2}
}

struct MPClassMetaData {
    var kind: Int
    var superClass: Any.Type?
    var cacheData: (Int, Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDesc: UnsafeMutablePointer<MPClassDesc>
    var iVarDestroyer: UnsafeRawPointer
}

struct MPClassDesc {
    var flags: UInt32
    var parent: MPRelativeDirtPointer<UnsafeRawPointer>
    var name: MPRelativeDirtPointer<CChar>
    var accessFunctionPointer: MPRelativeDirtPointer<UnsafeRawPointer>
    var fieldDescriptor: MPRelativeDirtPointer<MPFieldDesc>
    var superClassType: MPRelativeDirtPointer<CChar>
    var metadataNegativeSizeInWords: UInt32
    var metadataPositiveSizeInWords: UInt32
    var numImmediateMembers: UInt32
    var numFields: UInt32
    var fieldOffset: UInt32
    func getFieldOffsets(_ metadata: UnsafeRawPointer) -> UnsafePointer<Int> {
        return metadata.assumingMemoryBound(to: Int.self).advanced(by: numericCast(self.fieldOffset))
    }
    var genericOffset: Int { return 2 }
}

struct MPProtocolMetaData {
    let type: Any.Type
    let witness: Int
}

struct MPRelativeDirtPointer<Pointee>{
    var offset: Int32
    mutating func relativeOffset() -> UnsafeMutablePointer<Pointee>{
        let offset = self.offset
        return withUnsafePointer(to: &self) { p in
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self))
        }
    }
}

struct MPFieldDesc {
    var mangledTypeName: MPRelativeDirtPointer<CChar>
    var superClass: MPRelativeDirtPointer<CChar>
    var kind: UInt16
    var fieldRecordSize:UInt16
    var numFields: UInt32
    var fields: MPFiledRecordBuffer<MPFieldRecord>
}

struct MPFieldRecord {
    var flags: UInt32
    var mangledTypeName: MPRelativeDirtPointer<UInt8>
    var fieldName: MPRelativeDirtPointer<UInt8>
}

struct MPFiledRecordBuffer<Element>{
    var element: Element

    mutating func index(of i: Int) -> UnsafeMutablePointer<Element> {
        return withUnsafePointer(to: &self) {
            return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
        }
    }
}

func getBitCast(type: Any.Type) -> BrigeProtocol.Type {
    let container = MPProtocolMetaData(type: type, witness: 0)
    let bitCast = unsafeBitCast(container, to: BrigeProtocol.Type.self)
    return bitCast
}

protocol BrigeProtocol {}
extension BrigeProtocol {
    static func get(from pointer: UnsafeRawPointer) -> Any {
        return pointer.assumingMemoryBound(to: Self.self).pointee
    }
}

class MPConvertibleMetaData {
    
    private static var mp_metaDatas : [UInt : MPConvertibleModelInfo] = [:]
    
    private static let mp_metaDataLock : NSLock = NSLock()
        
    static func reloadStructInfo(_ type : Any.Type, key : UInt) -> MPConvertibleModelInfo {
        var list : [MPConvertiblePropertyInfo] = []
        let ptr = unsafeBitCast(type, to: UnsafeMutablePointer<MPStructMetaData>.self)
        let numFileds = ptr.pointee.typeDesc.pointee.numFields
        let bufferPtr = UnsafeBufferPointer(start: UnsafeRawPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self).advanced(by: numericCast(ptr.pointee.typeDesc.pointee.fieldOffset))).assumingMemoryBound(to: Int32.self), count: Int(ptr.pointee.typeDesc.pointee.numFields))
        for i in 0..<numFileds {
            let fieldRecord = ptr.pointee.typeDesc.pointee.fieldDescriptor.relativeOffset().pointee.fields.index(of: Int(i))
            let fieldName = fieldRecord.pointee.fieldName.relativeOffset()
            let mangledTypeName = fieldRecord.pointee.mangledTypeName.relativeOffset()
            let genericVector = UnsafeRawPointer(ptr).advanced(by: ptr.pointee.typeDesc.pointee.genericOffset * MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: Any.Type.self)
            
            let functionMap: [String: () -> Any.Type?] = [
                "function": { _getTypeByMangledNameInContext(mangledTypeName, 256, genericContext: UnsafeRawPointer(ptr.pointee.typeDesc), genericArguments: genericVector) }
            ]
            if let function = functionMap["function"],let fieldType = function() {
                let fieldOffset = bufferPtr[Int(i)]
                list.append(MPConvertiblePropertyInfo.init(offset: Int(fieldOffset), jsonKey: (String(cString: fieldName)), type: getBitCast(type: fieldType)))
            }
        }
        let modelInfo = MPConvertibleModelInfo()
        modelInfo.propertyList = list
        mp_updateMetaData(value: modelInfo, key: key)
        return modelInfo
    }
    
    static func reloadClassInfo(_ type : Any.Type, key : UInt, obj : MPConvertible) -> MPConvertibleModelInfo {
        var list : [MPConvertiblePropertyInfo] = []
        let ptr = mp_bitCast(type1: type, type2: UnsafeMutablePointer<MPClassMetaData>.self)
        let modelInfo : MPConvertibleModelInfo = MPConvertibleModelInfo()
        if let superType = ptr.pointee.superClass {
            let name = String(describing: superType)
            if !(name == "Swift._SwiftObject"
                || name == "NSObject"
                || name == "_TtCs12_SwiftObject") {
                modelInfo.superInfo = reloadModelInfo(superType, displayStyle: .class_style, obj: obj)
            }
        }
        let offsets = ptr.pointee.typeDesc.pointee.getFieldOffsets(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int8.self))
        for i in 0..<ptr.pointee.typeDesc.pointee.numFields {
            let fieldRecord = ptr.pointee.typeDesc.pointee.fieldDescriptor.relativeOffset().pointee.fields.index(of: Int(i))
            let fieldOffset = offsets[Int(i)]
            let fieldName = fieldRecord.pointee.fieldName.relativeOffset()
            let mangledTypeName = fieldRecord.pointee.mangledTypeName.relativeOffset()
            let genericVector = UnsafeRawPointer(ptr).advanced(by: ptr.pointee.typeDesc.pointee.genericOffset * MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: Any.Type.self)
            let fieldType = _getTypeByMangledNameInContext(mangledTypeName, 256, genericContext: UnsafeRawPointer(ptr.pointee.typeDesc), genericArguments: genericVector)
            let realType = unsafeBitCast(fieldType, to: Any.Type.self)
            list.append(MPConvertiblePropertyInfo.init(offset: fieldOffset, jsonKey: (String(cString: fieldName)), type: realType))
        }
        modelInfo.propertyList = list
        mp_updateMetaData(value: modelInfo, key: key)
        return modelInfo
    }
    
    static func reloadModelInfo(_ type : Any.Type, displayStyle : MPConvertibleDisplayStyle, obj : MPConvertible) -> MPConvertibleModelInfo {
        let key : UInt = unsafeBitCast(type, to: UInt.self)
        if let _modelInfo = mp_metaDataValue(key: key) {
            return _modelInfo
        }
        var modelInfo : MPConvertibleModelInfo
        switch displayStyle {
            case .class_style:
                modelInfo = reloadClassInfo(type, key: key, obj: obj)
            case .struct_style:
                modelInfo = reloadStructInfo(type, key: key)
        }
        if let propertyMap = obj.mp_propertyMapper() {
            var replaceInfo : MPConvertibleModelInfo? = modelInfo
            while replaceInfo != nil{
                for i in 0..<replaceInfo!.propertyList.count {
                    let propertyKey = replaceInfo!.propertyList[i].propertyKey
                    if propertyMap.keys.contains(propertyKey), let replaceKey = propertyMap[propertyKey] {
                        if replaceKey.count > 0 {
                            replaceInfo?.propertyList[i].replaceKey = replaceKey
                            replaceInfo?.propertyList[i].ignoreProperty = false
                        }else {
                            replaceInfo?.propertyList[i].replaceKey = nil
                            replaceInfo?.propertyList[i].ignoreProperty = true
                        }
                    }
                }
                replaceInfo = replaceInfo?.superInfo
            }
        }
        return modelInfo
    }
    
    static func mp_updateMetaData(value : MPConvertibleModelInfo, key : UInt) {
        mp_metaDataLock.lock()
        mp_metaDatas.updateValue(value, forKey: key)
        mp_metaDataLock.unlock()
    }
    
    static func mp_metaDataValue(key : UInt) -> MPConvertibleModelInfo? {
        var modelInfo : MPConvertibleModelInfo? = nil
        mp_metaDataLock.lock()
        modelInfo = mp_metaDatas[key]
        mp_metaDataLock.unlock()
        return modelInfo
    }
    
}

enum MPConvertibleDisplayStyle {
    case class_style
    case struct_style
}
